﻿using System;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using Comdiv.QWeb.Utils;

namespace Comdiv.QWeb.Serialization {
	public class XmlToBxlConverter {
		private static readonly Regex Literalregex = new Regex(@"^[\w@_?+\*\-/\d\.][\d\w_\.@]*$", RegexOptions.Compiled);
		private bool _firstAttribute;
		public bool InlineAttributesByDefault { get; set; }
		public string[] InlineAlwaysAttributes { get; set; }
		public string[] NewlineAlwaysAttributes { get; set; }
		public string[] FirstPlaceAttributes { get; set; }
		public string[] SkipAttributes { get; set; }
		public bool UseTrippleQuotOnValues { get; set; }
		protected TextWriter Output { get; set; }

		protected int Level { get; set; }

		public bool NoRootElement { get; set; }


		public void Write(XElement e, TextWriter output) {
			InlineAlwaysAttributes = InlineAlwaysAttributes ?? new[] {"id", "code", "name", "_file", "_line", "idx"};
			NewlineAlwaysAttributes = NewlineAlwaysAttributes ?? new string[] {};
			FirstPlaceAttributes = FirstPlaceAttributes ?? new[] {"id", "code", "name"};
			SkipAttributes = SkipAttributes ?? new string[] {};
			Level = 0;
			Output = output;
			if (NoRootElement)
			{
				foreach (var element in e.Elements()) {
					writeElement(element,true);
				}
			}
			else {
				writeElement(e, false);
			}
		}

		private void writeElement(XElement e, bool newline = true) {
			if (newline) {
				this.newline();
			}
			indent();
			Output.Write(e.Name.LocalName);
			Level++;
			_firstAttribute = true;
			var id = e.Attribute("id");
			var code = e.Attribute("code");
			var name = e.Attribute("name");
			var idval = e.id();
			var writecode = false;
			if (id != null && code != null && code.Value != id.Value) {
				writecode = true;
			}
			if (idval.hasContent()) {
				Output.Write(" " + escape(idval));
				_firstAttribute = false;
			}
			if (writecode && _firstAttribute) {
				Output.Write(" " + escape(code.Value));
				_firstAttribute = false;
			}
			if (name != null) {
				if (_firstAttribute) {
					Output.Write(" name=" + escape(name.Value));
					_firstAttribute = false;
				}
				else {
					Output.Write(", " + escape(name.Value));
				}
			}
			if (writecode && !_firstAttribute) {
				Output.Write(", code=" + escape(code.Value));
			}
			foreach (var a in e.Attributes().OrderBy(EvaluateOrderKey).Where(x => EvaluateOrderKey(x).StartsWith("00")).ToArray()
				) {
				if (a.Name == "id" || a.Name == "code" || a.Name == "name") continue;
				writeAttribute(a);
				_firstAttribute = false;
			}
			var selfstr = e.Nodes().OfType<XText>().Select(x => x.Value ?? "").concat(Environment.NewLine);
			if (selfstr.hasContent()) {
				selfstr = escape(selfstr, UseTrippleQuotOnValues);
				Output.Write(" : ");
				Output.Write(selfstr);
			}
			foreach (var a in e.Attributes().OrderBy(EvaluateOrderKey).Where(x => EvaluateOrderKey(x).StartsWith("01")).ToArray()
				) {
				if (a.Name == "id" || a.Name == "code" || a.Name == "name") continue;
				writeAttribute(a, true);
			}
			foreach (var e2 in e.Elements()) {
				writeElement(e2);
			}
			Level--;
		}

		private void writeAttribute(XAttribute a, bool tripple = false) {
			if (-1 != Array.IndexOf(SkipAttributes, a.Name.LocalName)) return;
			var inline = EvaluateOrderKey(a).StartsWith("00");
			if (inline) {
				if (!_firstAttribute) {
					Output.Write(",");
				}
				Output.Write(" ");
				Output.Write(a.Name.LocalName);
				Output.Write("=");
				Output.Write(escape(a.Value, false));
			}
			else {
				newline();
				indent();
				Output.Write(a.Name.LocalName);
				Output.Write("=");
				Output.Write(escape(a.Value, tripple));
			}
		}

		private string escape(string value, bool cantripple = false) {
			if (string.IsNullOrEmpty(value)) return "''";
			if (Literalregex.IsMatch(value)) return value;
			if ((value.Contains("\n") || value.Contains("\r")) && cantripple) {
				return "\"\"\"" + value + "\"\"\"";
			}
			if (value.Contains("\"")) {
				return "'" + value.Replace("\r", "\\r").Replace("\n", "\\n").Replace("\t", "\\t").Replace("'", "\\'") +
				       "'";
			}
			else {
				return "\"" + value.Replace("\r", "\\r").Replace("\n", "\\n").Replace("\t", "\\t") +
				       "\"";
			}
		}

		private void indent() {
			for (var i = 0; i < Level; i++) {
				Output.Write("\t");
			}
		}

		private void newline() {
			Output.Write(Environment.NewLine);
		}

		public string EvaluateOrderKey(XAttribute attribute) {
			var oa = attribute.Annotation<OrderAnnotation>();
			if (null != oa) return oa.Value;
			var name = attribute.Name.LocalName;
			var inlineorder = 1;
			if (InlineAttributesByDefault) {
				inlineorder = 0;
			}
			if (-1 != Array.IndexOf(InlineAlwaysAttributes, name)) {
				inlineorder = 0;
			}
			if (-1 != Array.IndexOf(NewlineAlwaysAttributes, name)) {
				inlineorder = 0;
			}
			var grouporder = Array.IndexOf(FirstPlaceAttributes, name);
			if (-1 == grouporder) {
				grouporder = 99;
			}

			var result = string.Format("{0:00}{1:00}{2}", inlineorder, grouporder, name.ToUpper());
			oa = new OrderAnnotation {Value = result};
			attribute.AddAnnotation(oa);
			return result;
		}

		public string Convert(XElement e) {
			var sw = new StringWriter();
			Write(e, sw);
			return sw.ToString();
		}

		#region Nested type: OrderAnnotation

		private class OrderAnnotation {
			public string Value;
		}

		#endregion
	}
}